#include <windows.h>
#include <process.h>
#include <tchar.h>

#include <vector>
#include "windows_display.h"


namespace detail
{
	static const int WM_DISPLAY_DRAW_MSG = WM_USER + 100;
	static unsigned __stdcall display_thread(void *the_display);
	static  windows_display_implementation_t* display_implementation_ = NULL;
	struct windows_display_implementation_t{
		windows_display_implementation_t(unsigned width, unsigned height){
			width_ = width;
			height_ = height;
			back_frame_data_ = new COLORREF[width*height];
			is_dirty_frame_ = true;

			hbitmap_ = NULL;
			hbitmapdc_ = NULL;
			::_beginthreadex(NULL,0,display_thread,this,0,&thread_id_);
		}
		void UpdateDisplay(const WindowsDisplay::display_info_t& frame_data)
		{
			if (back_frame_data_)
			{
				is_dirty_frame_ = true;
				current_frame_data_ = frame_data.frame_data;
				::PostThreadMessage(thread_id_,WM_DISPLAY_DRAW_MSG,NULL,NULL);
			}
		}
		void PrepareFrame()
		{
			if(!is_dirty_frame_) return;
			float rgb[3];
			for (size_t i = 0;i < (width_*height_) &&  i < current_frame_data_.size(); ++i)
			{
				convert_to_rgb(&current_frame_data_[i],rgb);
				back_frame_data_[i] = //BGR order reverse
					RGB(toInt(rgb[2]),toInt(rgb[1]),toInt(rgb[0]));
			}
			is_dirty_frame_ = false;
		}
		void DrawImage();
		void ReDrawWindow();
		void ReleaseResource();
		~windows_display_implementation_t();
		void InitializeDisplayWindow();
	private:
		static int toInt(double x)
		{
			return int(pow(1-exp(-x),1/2.2)*255+.5);
		} 
	private:
		HWND hwnd_;
		COLORREF *back_frame_data_;
		std::vector<spectrum_t> current_frame_data_;
		bool is_dirty_frame_;
		unsigned width_,height_;
		HBITMAP hbitmap_;
		HDC hbitmapdc_;
		unsigned thread_id_;
	};
	windows_display_implementation_t::~windows_display_implementation_t()
	{
		ReleaseResource();
	}
	void windows_display_implementation_t::ReleaseResource()
	{
		delete []back_frame_data_;
		back_frame_data_ = NULL;

		::DeleteObject(hbitmap_);
		::DeleteDC(hbitmapdc_);

		hbitmap_ = NULL;
		hbitmapdc_ = NULL;
	}
	void windows_display_implementation_t::ReDrawWindow()
	{
		RECT rt;
		::GetClientRect(hwnd_,&rt);
		::InvalidateRect(hwnd_,&rt,TRUE);
	}
	void windows_display_implementation_t::DrawImage()
	{
		//a simple ,stupid, inefficient way to show the image
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd_, &ps);
		unsigned w,h;
		w = width_;
		h = height_;
		if (NULL == hbitmap_)
			hbitmap_ = ::CreateBitmap(w,h,1,32,back_frame_data_);
		if (NULL == hbitmapdc_)
			hbitmapdc_ = ::CreateCompatibleDC(hdc);
		HBITMAP oldhbitmap = (HBITMAP)::SelectObject(hbitmapdc_, hbitmap_);

		::BitBlt(hdc, 0,0, w,h, hbitmapdc_, 0, 0 ,SRCCOPY);

		::SelectObject(hbitmapdc_,oldhbitmap);
		::DeleteObject(hbitmap_);
		::DeleteDC(hbitmapdc_);
		EndPaint(hwnd_, &ps);
		hbitmap_ = NULL;
		hbitmapdc_ = NULL;
	}

	static LRESULT CALLBACK wndproc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
	{
		switch(message)
		{
		case WM_PAINT:
			{
				if(display_implementation_)
				{
					display_implementation_->PrepareFrame();
					display_implementation_ ->DrawImage();
				}
			}
			break;
		case WM_DESTROY:
			::PostQuitMessage(0);
			break;
		default:
			return DefWindowProc(hwnd, message, wparam, lparam);
		}
		return 0;
	}
	static unsigned display_thread(void *the_display)
	{
		windows_display_implementation_t* display = reinterpret_cast<windows_display_implementation_t*>(the_display);
		display->InitializeDisplayWindow();
		MSG msg = {0};
		while( WM_QUIT != msg.message)
		{
			if(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				::TranslateMessage(&msg);
				::DispatchMessage(&msg);
			}
			if (msg.message == WM_DISPLAY_DRAW_MSG)
			{
				
				display->ReDrawWindow();
			}
			::Sleep(500);
		}
		display->ReleaseResource();
		return 0;
	}

	void windows_display_implementation_t::InitializeDisplayWindow()
	{
		// Register class
		WNDCLASSEX wcex;
		wcex.cbSize = sizeof( WNDCLASSEX );
		wcex.style = CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc = wndproc;
		wcex.cbClsExtra = 0;
		wcex.cbWndExtra = 0;
		wcex.hInstance = NULL;
		wcex.hIcon = NULL;
		wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
		wcex.hbrBackground = NULL;// ( HBRUSH )( COLOR_WINDOW + 1 );
		wcex.lpszMenuName = NULL;
		wcex.lpszClassName = _T("clpbr");
		wcex.hIconSm = NULL;
		if( !RegisterClassEx( &wcex ) )
			return ;

		// Create window
		RECT rc = { 0, 0, width_, height_ };
		AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
		int window_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZE |WS_MAXIMIZE; 
		hwnd_ = CreateWindow( _T("clpbr"), _T("clpbr display"), window_style,
			CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, NULL,
			NULL );
		if( !hwnd_)
			return ;

		ShowWindow( hwnd_, TRUE);
	}
}
WindowsDisplay::WindowsDisplay(unsigned width,unsigned height)
{
	display_ =  new detail::windows_display_implementation_t(width, height);
	detail::display_implementation_ = display_; 
}
WindowsDisplay::~WindowsDisplay()
{
	delete display_;
	detail::display_implementation_ = 0; 
}

void WindowsDisplay::UpdateDisplay(const WindowsDisplay::display_info_t& frame_data)
{
	display_->UpdateDisplay(frame_data);
}